from django.contrib.auth.models import User
from django.shortcuts import render_to_response, render
from django.template import RequestContext
from django.http import Http404, HttpResponse, HttpResponseRedirect
from django import forms
from collections import OrderedDict

from svn.models import Repository, Node
from userdata.models import UserData
from preference.models import SitePreference
from svn import command
from svn import extensions

import types
import base64
import mimetypes
import hashlib
import os
import socket
import subprocess
import time

chat_server = "http://"+socket.gethostbyname(socket.getfqdn())+":3100"
node_server = "http://"+socket.gethostbyname(socket.getfqdn())+":3000/channel"
class LoginForm(forms.Form):
    '''
    Login form definition. Used to generate HTML form in the main page.
    '''
    username = forms.CharField(label="", widget=forms.TextInput(attrs={'placeholder': 'Username', 'id':'id_login_username'}))
    password = forms.CharField(label="", widget=forms.PasswordInput(attrs={'placeholder': 'Password', 'id':'id_login_password'}))

class RegisterForm(forms.Form):
    '''
    Registration form definition. Used to generate HTML form in the main page. 
    '''
    email = forms.EmailField(label="", widget=forms.TextInput(attrs={'placeholder': 'Email'}))

    username = forms.CharField(label="", widget=forms.TextInput(attrs={'placeholder': 'Username'}))
    password = forms.CharField(label="", widget=forms.PasswordInput(attrs={'placeholder': 'Password'}))
    confirm_password = forms.CharField(label="", widget=forms.PasswordInput(attrs={'placeholder': 'Confirm Password'}))

def createElement(request):
    '''
    Create new folder/file request will be posted to this function. 
    '''
    if request.POST and request.user.is_superuser:
        get_repoid = request.POST.get('repoid', False)
        get_filepath = base64.urlsafe_b64decode(request.POST.get('path', False).encode("utf-8"))
        get_name = request.POST.get('name', False)
        get_type = request.POST.get('type', 'Folder')
        get_curr = request.POST.get('curr', False)
        
        # Dummy fix for ending # sign. However if the user manually modifies the url and 
        # deliberately tries to break the code this would not work and command.py will
        # catch this.
        if get_repoid.endswith('#'):
            get_repoid= get_repoid[:-1]

        curr_repo = Repository.objects.get(id=get_repoid)

        parentname = get_filepath.split("/")[-1]
        parentpath = get_filepath[:-(len(parentname)+1)]
        '''
        Idea here is to use timehash to prevent folder with same name being added at the same time with different url.
        I will just create another temp temp timehash for this node for creating purposes. 
        '''
        try:
            parentnodehash = Node.objects.get(path=parentpath, filename=parentname, repoid=get_repoid, kind='dir').timehash
        except Node.DoesNotExist:
            if curr_repo.root == get_filepath:
                hashable = hashlib.sha1()
                hashable.update(str(time.time()))
                parentnodehash = hashable.hexdigest()[:10]
            else:
                return HttpResponse('INVALID ACCESS')
        rtval = command.checkout_file(get_filepath, get_curr, curr_repo.username, curr_repo.password, parentnodehash, curr_repo.force_accept_certificate, {'name':get_name, 'type':get_type})
        return HttpResponse(rtval)

def register(request):
    '''
    View function that handles registration form submitted by user. 
    '''
    # Check if valid http post request and site open for registration
    if request.POST and allow_register():

        form = RegisterForm(request.POST)
        if form.is_valid():
            username = form.cleaned_data['username']
            password = form.cleaned_data['password']
            confirm_password = form.cleaned_data['confirm_password']
            email = form.cleaned_data['email']
            '''
            Form validation: Check whether password confirm is the same, user and email is not 
            already used. If no, create a new user and return an OK response.
            '''
            if not password == confirm_password:
                raise form.ValidationError("passwords dont match each other")
            try: 
                User.objects.get(username=username)
            except User.DoesNotExist:
                pass
            else:
                return HttpResponse("5559")

            try: 
                User.objects.get(email=email)
            except User.DoesNotExist:
                pass
            else:
                return HttpResponse("5560")

            new_user = User.objects.create_user(username, email, password)
            new_user.save()
            return HttpResponse("OK")
    return HttpResponse("Invalid Access")

def login(request):
    '''
    View function that handles login action. Log user in if a http post request containing
    the currect username and password pair is send to this function.

    Redirect user to svn page if successfully login, else return LOGIN ERROR response.
    '''
    if request.POST:
        form = LoginForm(request.POST)
        if form.is_valid():
            username = form.cleaned_data['username']
            password = form.cleaned_data['password']
            from django.contrib.auth import authenticate, login

            user = authenticate(username=username, password=password)

            if user is not None:
                if user.is_active:
                    login(request, user)
                    return HttpResponseRedirect('/svn/')
    return HttpResponse("LOGIN ERROR")

def logout(request):
    '''
    View function that terminate a user session. 
    '''
    from django.contrib.auth import logout
    logout(request)
    return HttpResponseRedirect('/svn/')

def index(request):
    '''
    View function that lists all repositories / contents under a specific folder. 
    '''
    
    #Get parameter from url. If not set, set to false.
    base64_parent_path = request.GET.get('repo', False)
    parent_name = request.GET.get('name', False)
    parent_id = request.GET.get('id',False)

    #Get site global settings: whether allows register or not
    allow_reg = allow_register()
    site_name = get_site_name()
    loggedin = request.user.is_authenticated()

    if not loggedin:
        loginform = LoginForm()
        registerform = RegisterForm()

    else:
        loginform = False
        registerform = False
    
    if((not base64_parent_path) and (not parent_name)  and (not parent_id) ):
        '''
        If all parameters are not set, that means the user has requested the list 
        of all repositories. 
        Also set 'showrepo' = True, which allows to use the same template for 
        both repo and contents.
        '''
        all_repositories = Repository.objects.all()
        rtval = []
        index = 0
        
        for repo in all_repositories:
            if loggedin or (not repo.is_private):
                rtval.append(OrderedDict())
                rtval[index]['label'] = repo.label
                rtval[index]['path'] = base64.urlsafe_b64encode(repo.root)
                rtval[index]['id'] = repo.id
                index = index+1


        return render_to_response('svn/index.html',{'rtval':rtval, 'loginform': loginform, 'registerform': registerform, 'showrepo': True, 'allow_reg': allow_reg, 'site_name':site_name, 'chat_server':chat_server }, context_instance=RequestContext(request))
    else:
        #If they are not default data, that means user has requested a specific path.
        parent_path = base64.urlsafe_b64decode(base64_parent_path.encode("utf-8"))
        
        root_repo = Repository.objects.get(id = parent_id)
        if parent_name:
            root = parent_path+'/'+parent_name
        else:
            root = parent_path
        rtval = render_directories(parent_id ,root, root_repo.root, root_repo.username, root_repo.password, root_repo.force_accept_certificate)

    if rtval == "5100":
        pass
    else:
        for item in rtval:
            try:
                currfile = Node.objects.get(path=base64.urlsafe_b64decode(item['hashed_path']), filename=item['name'], repoid=item['id'], kind=item['kind'])
            except Node.DoesNotExist:
                currfile = Node.objects.create(path=base64.urlsafe_b64decode(item['hashed_path']), filename=item['name'], repoid=item['id'], kind=item['kind'])

    return render_to_response('svn/index.html',{'rtval':rtval, 'loginform': loginform, 'registerform': registerform, 'allow_reg': allow_reg, 'site_name':site_name, 'repo_label': root_repo.label, 'chat_server':chat_server}, context_instance=RequestContext(request))

def file(request):
    '''
    Retrieve file from svn and output file content to text editor. 
    @TODO: ADD INVALID ACCESS WARNING
    '''
    site_name = get_site_name()
    #Get filepath, filename and parent id from http get. 
    get_filepath = base64.urlsafe_b64decode(request.GET.get('path',False).encode("utf-8"))
    get_filename = request.GET.get('name', False)
    get_parent_id = request.GET.get('id',False)
    #If all the parameters are set
    if(get_filepath and get_filename and get_parent_id):
        #Query the database for hashed timestamp field, if the file has never been used before, create corresponding database entry. 
        try:
            currfile = Node.objects.get(path=get_filepath, filename=get_filename, repoid=get_parent_id)
        except Node.DoesNotExist:
            currfile = Node.objects.create(path=get_filepath, filename=get_filename, repoid=get_parent_id, kind='file')
        
        #Query the database for the username and password of the repo
        repo = Repository.objects.get(id = get_parent_id)
        #Hash file info to create unique collaborative document editing session (Node.js)
        hashable = hashlib.sha1()
        hashable.update(get_filepath+get_parent_id+get_filename)
        documentid = hashable.hexdigest()[:10]
        #Get the document type to facilitate auto syntax highlighting, if type is not defined in the extension, set it as plain text
        doctype = extensions.getMode(os.path.splitext(get_filename)[1][1:])
        if doctype == "":
            doctype= "text"

        '''
        Detect whether the current user has write access to this file. 
        If is staff or superuser, grant access regardless whether the 
        user has the file on the list. 
        '''
        writableAccess = False
        if request.user.is_authenticated():
            try:
                userProfile = request.user.get_profile().writableAccess
                if userProfile.filter(pk=currfile.id):
                    writableAccess = True
            except UserData.DoesNotExist:
                pass
            if request.user.is_superuser or request.user.is_staff:
                writableAccess = True


        return render_to_response('svn/file.html',{
            'rtval':command.checkout_file(get_filepath, get_filename,repo.username, repo.password, currfile.timehash, repo.force_accept_certificate),
            'repoid':get_parent_id,'filepath':get_filepath, 'filename':get_filename, 
        'mimetype':mimetypes.guess_type(get_filename) ,'doctype': doctype, 'documentid':documentid, 'writableAccess': writableAccess, 'site_name':site_name, 'node_server': node_server, 'chat_server': chat_server}, context_instance=RequestContext(request))
    else:
        return Http404
    
def savefile(request):
    '''
    Ajax handler that saves the content of the editor to file
    '''
    if not request.POST:
        return Http404
    content_to_save = request.POST.get('content', False)
    get_repoid = request.POST.get('repoid', False)
    get_filepath = request.POST.get('path', False)
    get_filename = request.POST.get('filename', False)
    get_commitmessage = request.POST.get('commit', False)
    repo = Repository.objects.get(id = get_repoid)
    currfile = Node.objects.get(path=get_filepath, filename=get_filename, repoid=get_repoid)
    #If post message contains commit info, that means users wish to commit the change to svn. Else only save to local file
    if(get_commitmessage):
        out = command.savefile(content_to_save, get_filepath, get_filename, repo.username, repo.password, currfile.timehash, get_commitmessage)
        return HttpResponse(out)
    else:
        command.savefile(content_to_save, get_filepath, get_filename, repo.username, repo.password, currfile.timehash)
    return HttpResponse("OK")

def resetchange(request):
    '''
    Ajax handler that resets the current file back to the version on svn.
    '''
    if not request.POST:
        return Http404

    get_repoid = request.POST.get('repoid', False)
    get_filepath = request.POST.get('path', False)
    get_filename = request.POST.get('filename', False)

    repo = Repository.objects.get(id = get_repoid)
    currfile = Node.objects.get(path=get_filepath, filename=get_filename, repoid=get_repoid)

    command.reset_change(get_filepath, get_filename, repo.username, repo.password, currfile.timehash)

    return HttpResponse(command.reset_change(get_filepath, get_filename, repo.username, repo.password, currfile.timehash), content_type="text/plain")

def render_directories(repoid, path, root_path, username, password, force_accept_certificate):
    '''
        Request contents under a specific path. 
    '''
    repo_label = Repository.objects.get(id=repoid).label
    ordered_list=command.get_next_level(path, username, password, force_accept_certificate)
    if ordered_list == "5100":
        return ordered_list
    root_length = len(root_path)
    relative_path = path[root_length:]
    directory_list = relative_path.split("/")
    # If return a list, that means there are 0 or more than 1 content in the return value.
    if isinstance(ordered_list, types.ListType):
        rtval = []
        for item in ordered_list:
            #string op vars
            temp = {}
            temp['path'] = OrderedDict()
            for idx, dir in enumerate(directory_list):
                curr_dir = ""
                for index, pre_dir in enumerate(directory_list[1:idx]):
                    
                    curr_dir=curr_dir+"/"+pre_dir
                #curr_dir=curr_dir+"/"+dir
                #if dir == "":
                    #dir = repo_label
                temp['path'][dir] = base64.urlsafe_b64encode(root_path+curr_dir)
                
            temp['kind'] = item['@kind']
            temp['name'] = item['name']
            temp['hashed_path'] = base64.urlsafe_b64encode(path)
            temp['id'] = repoid
            try:
                author = item['commit']['author']
            except KeyError:
                author = u''

            temp['commit'] = {'author':author , 'revision':item['commit']['@revision'], 'date':item['commit']['date']}
            rtval.append(temp)
        rtval = sorted(rtval, key=lambda k: k['kind'])
    else:
        #If returned a dictionary in OrderedList type, that means only one folder/file is returned. 
        rtval =[]
        temp = {}
        temp['path'] = OrderedDict()
        for idx, dir in enumerate(directory_list):
            curr_dir = ""
            for index, pre_dir in enumerate(directory_list[1:idx]):
                curr_dir=curr_dir+"/"+pre_dir
            temp['path'][dir] = base64.urlsafe_b64encode(root_path+curr_dir)
        temp['kind'] = ordered_list['@kind']
        temp['name'] = ordered_list['name']
        temp['hashed_path'] = base64.urlsafe_b64encode(path)
        temp['id'] = repoid
        try:
            author = ordered_list['commit']['author']
        except KeyError:
            author = u''

        temp['commit'] = {'author':author , 'revision':ordered_list['commit']['@revision'], 'date':ordered_list['commit']['date']}
        rtval.append(temp)
    return rtval

def get_site_name():
    '''
    Helper function queries the database for site name setting. If site name configuration is present, set site name as that one. 
    '''
    site_name = "Project Showcase"
    try:
        obj_site_name = SitePreference.objects.get(key='site_name')
        site_name = obj_site_name.value
    except SitePreference.DoesNotExist:
        pass

    return site_name

def allow_register():
    '''
    Helper function checks whether the settings allow registration. 
    If settings does not exist, do not allow registration.
    '''
    allow_reg = False
    try:
        reg_obj = SitePreference.objects.get(key='allow_reg')
        if reg_obj.value == 'True':
            allow_reg = True
    except SitePreference.DoesNotExist:
        pass

    return allow_reg